package com.example.rwq_test.webrtc.doubao;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class PlayerHandler extends TextWebSocketHandler {

    private static Logger log = LoggerFactory.getLogger(PlayerHandler.class);
    private static final Gson gson = new GsonBuilder().create();
    private final ConcurrentHashMap<String, UserSession> users = new ConcurrentHashMap<>();
    private final AtomicInteger portCounter = new AtomicInteger(5000);

    // 【新增】服务器局域网IP缓存
    private static String serverLanIp = null;

    public PlayerHandler() {
        // 在构造函数中获取IP地址
        serverLanIp = getLocalIpAddress();
        log.info("***************************************************");
        log.info("Server LAN IP detected: {}", serverLanIp);
        log.info("Please use this IP to access from other devices in the same LAN.");
        log.info("***************************************************");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        stop(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
        log.debug("Incoming message for session {}: {}", session.getId(), jsonMessage);

        String id = jsonMessage.get("id").getAsString();
        log.info("Processing instruction ID: {} for session {}", id, session.getId());
        switch (id) {
            case "start":
                start(session, jsonMessage);
                break;
            case "stop":
                stop(session);
                break;
            case "onIceCandidate":
                // 【改动】处理收到的ICE Candidate
                handleOnIceCandidate(session, jsonMessage);
                break;
            case "loadAndPlay":
                String filePath = jsonMessage.get("file").getAsString();
                log.info("Received loadAndPlay for file: {} in session {}", filePath, session.getId());
                loadFile(session, filePath);
                play(session);
                break;
            case "pause":
                log.info("Received pause for session {}", session.getId());
                pause(session);
                break;
            default:
                log.warn("Invalid ID: {} for session {}", id, session.getId());
                sendError(session, "Invalid id: " + id);
        }
    }

    private void start(WebSocketSession session, JsonObject jsonMessage) {
        try {
            UserSession user = new UserSession();
            user.setSession(session); // 【新增】保存 session
            users.put(session.getId(), user);

            JsonObject sdpOfferObj = jsonMessage.getAsJsonObject("sdpOffer");
            if (sdpOfferObj == null) throw new IllegalArgumentException("Missing or invalid sdpOffer");
            String sdpOffer = sdpOfferObj.get("sdp").getAsString();
            if (sdpOffer == null) throw new IllegalArgumentException("Missing sdp in sdpOffer");

            log.info("Incoming parsed successfully for session {}. Offer SDP (m-lines preview): {}", session.getId(), extractMLines(sdpOffer));

            int audioPort = portCounter.getAndAdd(4);
            int videoPort = audioPort + 2;

            // 【改动】使用动态获取的局域网IP生成SDP
            String sdpAnswer = generateSdpAnswer(serverLanIp, audioPort, videoPort);
            log.debug("Generated SDP Answer for session {}: {}", session.getId(), sdpAnswer);

            JsonObject response = new JsonObject();
            response.addProperty("id", "startResponse");
            response.addProperty("sdpAnswer", sdpAnswer);
            sendMessage(session, response);

            user.setAudioPort(audioPort);
            user.setVideoPort(videoPort);
            log.info("Session {} started with audio port {} video port {}", session.getId(), audioPort, videoPort);

            // 【新增】关键步骤: 主动发送服务器的ICE Candidate给浏览器
            // 因为FFmpeg不是WebRTC端，它不会自己生成candidate，我们必须手动模拟一个
            sendServerCandidate(user, serverLanIp, audioPort, "0"); // for audio, mid=0
            sendServerCandidate(user, serverLanIp, videoPort, "1"); // for video, mid=1

        } catch (Throwable t) {
            log.error("Start failed for session {}: {}", session.getId(), t.getMessage(), t);
            sendError(session, t.getMessage());
        }
    }

    // 【新增】处理收到的 Candidate 的方法
    private void handleOnIceCandidate(WebSocketSession session, JsonObject jsonMessage) {
        UserSession user = users.get(session.getId());
        if (user != null) {
            JsonObject candidateJson = jsonMessage.getAsJsonObject("candidate");
            IceCandidate candidate = gson.fromJson(candidateJson, IceCandidate.class);
            log.info("-> Received ICE Candidate from client [{}]: {}", session.getId(), candidate.getCandidate());
            // 在当前架构下，我们不需要对收到的candidate做任何事，因为FFmpeg的目标地址是固定的。
            // 在一个真正的媒体服务器中，这里会调用 `peerConnection.addIceCandidate(candidate)`。
        }
    }

    // 【新增】发送服务器自己的（模拟的）ICE Candidate
    private void sendServerCandidate(UserSession user, String ip, int port, String sdpMid) {
        JsonObject candidatePayload = new JsonObject();
        // 这是一个典型的 host candidate 格式
        candidatePayload.addProperty("candidate", String.format("candidate:1 1 UDP 2122252543 %s %d typ host", ip, port));
        candidatePayload.addProperty("sdpMLineIndex", Integer.parseInt(sdpMid));
        candidatePayload.addProperty("sdpMid", sdpMid);

        JsonObject response = new JsonObject();
        response.addProperty("id", "iceCandidate");
        response.add("candidate", candidatePayload);

        log.info("<- Sending Server ICE Candidate to client [{}]: {}", user.getSession().getId(), candidatePayload.get("candidate").getAsString());
        sendMessage(user.getSession(), response);
    }

    // loadFile 方法没有变化...
    private void loadFile(WebSocketSession session, String filePath) {
        UserSession user = users.get(session.getId());
        if (user == null || (user.getVideoProcess() != null && user.getAudioProcess() != null)) {
            log.warn("Cannot load file for session {}: user null or processes running", session.getId());
            return;
        }

        try {
            int audioPort = user.getAudioPort();
            int videoPort = user.getVideoPort();
            int videoSsrc = (int) (Math.random() * 0x7FFFFFFFL);
            int audioSsrc = (int) (Math.random() * 0x7FFFFFFFL);

            // 【改动】将RTP流发送到服务器的局域网IP，而不是127.0.0.1
            String rtpUrlVideo = String.format("rtp://%s:%d", serverLanIp, videoPort);
            String rtpUrlAudio = String.format("rtp://%s:%d", serverLanIp, audioPort);

            ProcessBuilder videoPb = new ProcessBuilder(
                    "ffmpeg", "-re", "-i", filePath,
                    "-map", "0:v", "-c:v", "libvpx", "-b:v", "500k", "-deadline", "realtime", "-cpu-used", "5",
                    "-payload_type", "96", "-ssrc", String.valueOf(videoSsrc),
                    "-f", "rtp", rtpUrlVideo
            );
            videoPb.redirectErrorStream(true);
            Process videoProcess = videoPb.start();
            user.setVideoProcess(videoProcess);

            ProcessBuilder audioPb = new ProcessBuilder(
                    "ffmpeg", "-re", "-i", filePath,
                    "-map", "0:a", "-c:a", "libopus", "-ar", "48000", "-ac", "2", "-b:a", "128k",
                    "-payload_type", "111", "-ssrc", String.valueOf(audioSsrc),
                    "-f", "rtp", rtpUrlAudio
            );
            audioPb.redirectErrorStream(true);
            Process audioProcess = audioPb.start();
            user.setAudioProcess(audioProcess);

            log.info("Video FFmpeg started for session {}: SSRC=0x{}, sending to {}", session.getId(), Integer.toHexString(videoSsrc), rtpUrlVideo);
            log.info("Audio FFmpeg started for session {}: SSRC=0x{}, sending to {}", session.getId(), Integer.toHexString(audioSsrc), rtpUrlAudio);
            log.info("FFmpeg PIDs: video={}, audio={} for session {}", videoProcess.pid(), audioProcess.pid(), session.getId());

            new Thread(() -> logFFmpegOutput(videoProcess, session.getId(), "video")).start();
            new Thread(() -> logFFmpegOutput(audioProcess, session.getId(), "audio")).start();
            new Thread(() -> waitForProcess(videoProcess, session, user, "video")).start();
            new Thread(() -> waitForProcess(audioProcess, session, user, "audio")).start();

        } catch (IOException e) {
            log.error("FFmpeg start IOException for session {}: {}", session.getId(), e.getMessage(), e);
            sendError(session, "FFmpeg start failed: " + e.getMessage());
        }
    }

    // ... 其他方法 (logFFmpegOutput, waitForProcess, play, pause 等) 保持不变 ...
    private void logFFmpegOutput(Process process, String sessionId, String type) {
        try (java.util.Scanner scanner = new java.util.Scanner(process.getInputStream())) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                log.info("FFmpeg {} output for session {}: {}", type, sessionId, line);
            }
        } catch (Exception e) {
            log.error("FFmpeg {} log read failed for session {}", type, sessionId, e);
        }
    }

    private void waitForProcess(Process process, WebSocketSession session, UserSession user, String type) {
        try {
            int exitCode = process.waitFor();
            log.info("FFmpeg {} exited with code {} for session {}", type, exitCode, session.getId());
            if (exitCode != 0) {
                sendError(session, "FFmpeg " + type + " exited with error code " + exitCode);
            }
            if (type.equals("video") && user.getAudioProcess() != null) {
                sendPlayEnd(session);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void play(WebSocketSession session) {
        log.info("Play called for session {} (loaded file, stream started)", session.getId());
    }

    private void pause(WebSocketSession session) {
        UserSession user = users.get(session.getId());
        if (user != null) {
            if (user.getVideoProcess() != null) {
                user.getVideoProcess().destroyForcibly();
            }
            if (user.getAudioProcess() != null) {
                user.getAudioProcess().destroyForcibly();
            }
            log.info("FFmpeg paused (stopped) for session {}", session.getId());
        } else {
            log.warn("No processes to pause for session {}", session.getId());
        }
    }


    // 【改动】方法签名增加 serverIp 参数
    private String generateSdpAnswer(String serverIp, int audioPort, int videoPort) {
        String fingerprint = "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:FC:D4:9A:A2:6B:2E:8F:1F:1E:0F:0C:FC:59:7F";
        String ufrag = "dummyufragtest";
        String pwd = "dummypwd32charslongtest";

        return "v=0\r\n" +
                "o=- 0 0 IN IP4 " + serverIp + "\r\n" + // 【改动】使用动态IP
                "s=-\r\n" +
                "c=IN IP4 " + serverIp + "\r\n" + // 【改动】使用动态IP
                "t=0 0\r\n" +
                "a=group:BUNDLE 0 1\r\n" +
                "a=msid-semantic: WMS *\r\n" +
                "a=ice-ufrag:" + ufrag + "\r\n" +
                "a=ice-pwd:" + pwd + "\r\n" +
                "a=fingerprint:sha-256 " + fingerprint + "\r\n" +
                "a=setup:passive\r\n" +
                "m=audio " + audioPort + " UDP/TLS/RTP/SAVPF 111\r\n" +
                "a=mid:0\r\n" +
                "a=rtpmap:111 opus/48000/2\r\n" +
                "a=sendrecv\r\n" +
                "a=rtcp-mux\r\n" +
                "a=rtcp-rsize\r\n" +
                "a=rtcp-fb:111 transport-cc\r\n" +
                "m=video " + videoPort + " UDP/TLS/RTP/SAVPF 96\r\n" +
                "a=mid:1\r\n" +
                "a=rtpmap:96 VP8/90000\r\n" +
                "a=sendrecv\r\n" +
                "a=rtcp-mux\r\n" +
                "a=rtcp-rsize\r\n" +
                "a=rtcp-fb:96 goog-remb\r\n" +
                "a=rtcp-fb:96 transport-cc\r\n" +
                "a=rtcp-fb:96 ccm fir\r\n" +
                "a=rtcp-fb:96 nack\r\n" +
                "a=rtcp-fb:96 nack pli\r\n";
    }

    // ... 其他方法 (extractMLines, stop, sendError, etc.) 保持不变 ...
    private String extractMLines(String sdp) {
        return sdp.lines().filter(line -> line.startsWith("m=")).collect(Collectors.joining("\n"));
    }

    private void stop(WebSocketSession session) {
        UserSession user = users.remove(session.getId());
        if (user != null) {
            user.release();
            log.info("Session {} stopped and resources released", session.getId());
        }
    }

    private void sendError(WebSocketSession session, String msg) {
        log.warn("Sending error to session {}: {}", session.getId(), msg);
        JsonObject response = new JsonObject();
        response.addProperty("id", "error");
        response.addProperty("message", msg);
        sendMessage(session, response);
    }

    private void sendMessage(WebSocketSession session, JsonObject json) {
        try {
            session.sendMessage(new TextMessage(json.toString()));
            log.debug("Message sent to session {}: {}", session.getId(), json);
        } catch (IOException e) {
            log.error("Send failed for session {}", session.getId(), e);
        }
    }

    private void sendPlayEnd(WebSocketSession session) {
        log.info("Sending playEnd to session {}", session.getId());
        JsonObject response = new JsonObject();
        response.addProperty("id", "playEnd");
        sendMessage(session, response);
    }

    // 【新增】获取本机局域网IP地址的工具方法
    private static String getLocalIpAddress() {
        try {
            for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) {
                for (InetAddress address : Collections.list(ni.getInetAddresses())) {
                    if (!address.isLoopbackAddress() && address instanceof Inet4Address) {
                        return address.getHostAddress();
                    }
                }
            }
        } catch (Exception e) {
            log.error("Could not find a non-loopback address", e);
        }
        log.warn("Could not find a non-loopback address, falling back to 127.0.0.1");
        return "127.0.0.1";
    }
}